package se.slackers.sounder.ui;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.concurrent.Callable;

import javax.swing.JComponent;

import se.slackers.sounder.algorithm.Rule;
import se.slackers.sounder.sound.SoundState;

@SuppressWarnings("serial")
public class Field extends JComponent implements MouseListener, Runnable, Callable<Void> {
	public static final int SIZE = 20;

	public static final float LIVE = 1;
	public static final float DEAD = 0;

	private int interval = 100;

	private final int xtiles;
	private final int ytiles;
	private final float[] tiles;
	private final float[] backbuffer;

	private Thread thread = null;
	private final Rule rule;
	private final SoundState generator;

	public Field(final int xtiles, final int ytiles, final SoundState generator, final Rule rule) {
		this.xtiles = xtiles;
		this.ytiles = ytiles;
		this.rule = rule;
		this.generator = generator;

		this.tiles = new float[xtiles * ytiles];
		this.backbuffer = new float[tiles.length];

		generator.init(xtiles, ytiles);

		clear();
		setSize(xtiles * SIZE, ytiles * SIZE);

		try {
			call();
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}

		addMouseListener(this);
	}

	public void setInterval(final int interval) {
		this.interval = Math.max(1, Math.min(interval, 5000));
	}

	public void setParam(final int n, final float value) {
		rule.param(n, value);
	}

	@Override
	public void paint(final Graphics g) {
		final int w = getWidth();
		final int h = getHeight();

		g.setColor(Color.black);
		g.fillRect(0, 0, w, h);

		int i = 0;
		for (int y = 0; y < ytiles; y++) {
			for (int x = 0; x < xtiles; x++) {

				final float value = tiles[i++];

				final int c = (int) (value * 0xff);
				g.setColor(new Color(c + (c << 8) + (c << 16)));
				g.fillRect(x * SIZE, y * SIZE, SIZE, SIZE);
				//
				// if (tiles[i++] == LIVE) {
				// g.setColor(Color.white);
				// g.fillRect(x * SIZE, y * SIZE, SIZE, SIZE);
				// }
				g.setColor(Color.gray);
				g.drawRect(x * SIZE, y * SIZE, SIZE, SIZE);
			}
		}
	}

	public void play() {
		thread = new Thread(this, "AutomataJPanel Thread");
		thread.start();
	}

	public void pause() {
		thread.interrupt();
		thread = null;
	}

	public void run() {
		while (true) {
			synchronized (tiles) {
				rule.compute(tiles, backbuffer, xtiles, ytiles);
				for (int i = 0; i < tiles.length; i++) {
					tiles[i] = backbuffer[i];
				}
				generator.update(tiles);
			}
			repaint();
			// exit thread on interrupt
			try {
				Thread.sleep(interval);
			} catch (final InterruptedException e) {
				return;
			}
		}
	}

	public final void clear() {
		synchronized (tiles) {
			for (int i = 0; i < tiles.length; i++) {
				tiles[i] = DEAD;
			}
		}
	}

	private void toggle(final int x, final int y) {
		synchronized (tiles) {
			final float value = tiles[x + y * xtiles];
			if (value < 0.5f) {
				tiles[x + y * xtiles] = LIVE;
			} else {
				tiles[x + y * xtiles] = DEAD;
			}
		}
		repaint();
	}

	public void mouseClicked(final MouseEvent e) {
		final int x = e.getX() / SIZE;
		final int y = e.getY() / SIZE;

		switch (e.getButton()) {
		case MouseEvent.BUTTON1:
			if (0 <= x && x < xtiles && 0 <= y && y < ytiles) {
				toggle(x, y);
			}
			break;
		case MouseEvent.BUTTON2:
			glider(x, y);
			break;
		case MouseEvent.BUTTON3:
			chaos(x, y);
			break;
		}
	}

	private void glider(final int x, final int y) {
		synchronized (tiles) {
			final int index = x + y * xtiles;
			tiles[index] = LIVE;
			tiles[index + 1] = LIVE;
			tiles[index + xtiles - 1] = LIVE;
			tiles[index + xtiles] = LIVE;
			tiles[index - xtiles - 1] = LIVE;
		}
		repaint();
	}

	public Void call() throws Exception {
		final int x = (int) (Math.random() * (xtiles - 2) + 1);
		final int y = (int) (Math.random() * (ytiles - 2) + 1);
		glider(x, y);
		chaos(x, y);
		return null;
	}

	private void chaos(final int x, final int y) {
		synchronized (tiles) {
			for (int i = 0; i < 10; i++) {
				final int xp = (int) (x + (Math.random() - 0.5) * 5);
				final int yp = (int) (y + (Math.random() - 0.5) * 5);
				final float value = 1.0f;

				if (0 <= xp && xp < xtiles && 0 <= yp && yp < ytiles) {
					tiles[xp + yp * xtiles] = value;
				}
			}
		}
		repaint();
	}

	public void mouseEntered(final MouseEvent arg0) {
	}

	public void mouseExited(final MouseEvent arg0) {
	}

	public void mousePressed(final MouseEvent arg0) {
	}

	public void mouseReleased(final MouseEvent arg0) {
	}
}
